package com.linshengjian.snowball.guard.filter;

import com.linshengjian.snowball.guard.GuardProperties;
import com.linshengjian.snowball.guard.exception.VerifyCodeErrorException;
import com.linshengjian.snowball.guard.handler.IVerifyCodeHandle;
import com.linshengjian.snowball.guard.service.IVerifyCodeService;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.GenericFilterBean;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class VerifyCodeFilter extends GenericFilterBean {

    public static final String VerifyObjectSession = "verify-object-session";

    private class VerifyCode {
        private String url;
        private String recipient;
        private String code;
        private Date time;

        public VerifyCode(String url, String recipient, String code, Date time) {
            this.url = url;
            this.recipient = recipient;
            this.code = code;
            this.time = time;
        }

        public String getUrl() {
            return url;
        }

        public void setUrl(String url) {
            this.url = url;
        }

        public String getRecipient() {
            return recipient;
        }

        public void setRecipient(String recipient) {
            this.recipient = recipient;
        }

        public String getCode() {
            return code;
        }

        public void setCode(String code) {
            this.code = code;
        }

        public Date getTime() {
            return time;
        }

        public void setTime(Date time) {
            this.time = time;
        }
    }

    private IVerifyCodeHandle handle;

    private IVerifyCodeService service;

    // 发送验证码请求的地址
    private RequestMatcher sendVerifyCodeRequestMatcher;

    // 验证验证码请求的地址
    private RequestMatcher validateVerifyCodeRequestMatcher;

    // 登录地址
    private RequestMatcher loginRequestMatcher;

    // 需要进行验证码验证的路径
    private List<RequestMatcher> verifyRequestPath = new ArrayList<>();

    private GuardProperties propertis;

    public VerifyCodeFilter(GuardProperties properties, IVerifyCodeHandle handle, IVerifyCodeService service) {

        this.handle = handle;
        this.service = service;
        this.propertis = properties;

        setSendVerifyCodeUrl(properties.getVerifyCode().getSend()); // 验证发送
        setValidateVerifyCodeUrl(properties.getVerifyCode().getValidate()); // 验证码验证

        loginRequestMatcher = new AntPathRequestMatcher(properties.getLogin().getUrl().getLogin());
        addVerifyPath(loginRequestMatcher);
    }

    private void validate(HttpServletRequest request, String url, String code) throws Exception {
        VerifyCode verifyCode = (VerifyCodeFilter.VerifyCode) request.getSession().getAttribute(url);

        if (verifyCode == null)
            throw new VerifyCodeErrorException("还没发送过验证码");

        if (!verifyCode.getCode().equals(code)) {
            throw new VerifyCodeErrorException("验证码错误");
        }

        if (System.currentTimeMillis() - verifyCode.getTime().getTime() > propertis.getVerifyCode().getExpiration() * 1000)
            throw new VerifyCodeErrorException("验证码超时");
//
//        request.getSession().setAttribute(VerifyObjectSession, verifyCode.getRecipient());
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {

        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;

        request.getSession().removeAttribute(VerifyObjectSession);

        if (sendVerifyCodeRequestMatcher.matches(request)) {

            String recipient = service.recipient(request);
            String url = request.getParameter(propertis.getVerifyCode().getUrlField());

            // 生成验证码
            String code = service.generate();
            try {
                service.send(recipient, code);
                // 验证码保存到session中
                request.getSession().setAttribute(url, new VerifyCode(url, recipient, code, new Date()));
                handle.sendSuccess(request, response);
            } catch (Exception e) {
                handle.sendError(request, response, e);
            }
            return;
        }

        if (validateVerifyCodeRequestMatcher.matches(request)) {
            // 验证验证码是否正确
            String url = request.getParameter(propertis.getVerifyCode().getUrlField());
            String code = request.getParameter(propertis.getVerifyCode().getCodeField());
            try {
                validate(request ,url, code);
                handle.validateSuccess(request, response);
            } catch (Exception e) {
                handle.validateError(request, response, e);
            }
            return;
        }

        // 获取header中的验证码
        String code = request.getHeader(propertis.getVerifyCode().getCodeField());
        if (loginRequestMatcher.matches(request) && StringUtils.isEmpty(code)) {
            chain.doFilter(request, response);
            return;
        }

        for (RequestMatcher matcher :verifyRequestPath) {
            if (matcher.matches(request)) {
                // 验证验证码，通过就执行下一个过滤器，不通过就返回
                String url = ((AntPathRequestMatcher)matcher).getPattern();
                try {
                    validate(request, ((AntPathRequestMatcher)matcher).getPattern(), code);
                    VerifyCode data = (VerifyCodeFilter.VerifyCode) request.getSession().getAttribute(url);
                    request.getSession().setAttribute(VerifyObjectSession, data.getRecipient());
                    request.getSession().removeAttribute(url);
                    break;
                } catch (Exception e) {
                    handle.validateError(request, response, e);
                    return;
                }
            }
        }

        chain.doFilter(request, response);
    }

    protected void addVerifyPath(RequestMatcher matcher) {
        this.verifyRequestPath.add(matcher);
    }

    public void addVerifyPath(String path) {
        RequestMatcher matcher = new AntPathRequestMatcher(path);
        addVerifyPath(matcher);
    }

    public void addVerifyPath(String method, String path) {
        RequestMatcher matcher = new AntPathRequestMatcher(path, method.toUpperCase());
        addVerifyPath(matcher);
    }

    public void setSendVerifyCodeUrl(String url) {
        this.sendVerifyCodeRequestMatcher = new AntPathRequestMatcher(url);
    }

    public void setValidateVerifyCodeUrl(String url) {
        this.validateVerifyCodeRequestMatcher = new AntPathRequestMatcher(url);
    }
}
